package org.layuiframework.stereotype.aop;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.layuiframework.stereotype.exception.APIException;
import org.layuiframework.stereotype.util.SpringContextUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

@Aspect
@Component
@Slf4j
public class ImportFileAopByEasyExcel {

    @Pointcut("@annotation(org.layuiframework.stereotype.annotation.ImportFromFile)")
    public void importFromFile(){

    }

    /**
     * 文件导入功能前置事件
     * @desc
     *      文件导入的方法：
     *      1. 需要传入文件源  MultipartFile 和 一个dto集合，方法可以是空的
     *      这一步是将excel文件中的信息读取出来，放到那个空的集合中
     *
     *      2. 然后目标方法实际执行，可以把这个集合进行修改
     *
     *      3. 最后在后置事件中将集合里面数据批量的导入到数据库中
     *
     *      这个方法也必须放在mybaitsplus的service实现类中才得行
     *
     * @param point
     */
    @Before(value = "importFromFile()")
    public void beforeImportFileAdvice(JoinPoint point){
        /**
         * 1. 获取mybaitsplus的service实现类中的dto泛型
         */
        /**
         * 检查类是否是mybaitsplus的service实现类
         * 如果不是就不继续处理
         */
        Class clazz = point.getSignature().getDeclaringType();
        if(!ServiceImpl.class.isAssignableFrom(clazz)) return;

        /**
         * 获取当前service里面的dto泛型
         */
        ParameterizedType parameterizedType = (ParameterizedType)clazz.getGenericSuperclass();
        Type[] types = parameterizedType.getActualTypeArguments();
        if(types.length > 1){
            Type type = parameterizedType.getActualTypeArguments()[1];
            try {
                /**
                 * 根据泛型从参数列表中找到dto实例
                 */
                Class c = Class.forName(type.getTypeName());
                /**
                 * 在参数列表中找出文件源 MultipartFile
                 */
                MultipartFile file = null;
                Object[] args = point.getArgs();
                for (Object o: args) {
                    if(MultipartFile.class.isAssignableFrom(o.getClass())) file = MultipartFile.class.cast(o);
                }
                if(null != file){
                    /**
                     * 找到文件源和dto泛型，执行方法查出excel文件里面的数据，并将这个数据集合放入 参数列表args中
                     */
                    for(int i = 0; i < args.length; i++) {
                        if(List.class.isAssignableFrom(args[i].getClass())){
                            List.class.cast(args[i]).addAll(doRead(file, c));
                        }
                    }
                }
            } catch (Exception e) {
                log.error("前置事件执行错误!", e);
                throw new APIException("前置事件执行错误!");
            }
        }
    }

    @AfterReturning(value = "importFromFile()" , returning = "returning")
    public void afterImportFileAdvice(JoinPoint point, Object returning){
        /**
         * 检查类是否是mybaitsplus的service实现类
         * 如果不是就不继续处理
         */
        Class clazz = point.getSignature().getDeclaringType();
        if(!ServiceImpl.class.isAssignableFrom(clazz)) return;
        try{
            if(List.class.isAssignableFrom(returning.getClass())){
                List<?> list = List.class.cast(returning);
                if(list.isEmpty() || list.size() == 0) throw new APIException("easyExcel读取excel文件为空!");
                /**
                 * 获取serviceimpl 调用方法批量导入
                 */
                ServiceImpl serviceImpl = (ServiceImpl) SpringContextUtil.getBean(clazz);
                Object entry = list.remove(list.size() - 1);
                if(list.size() > 0 && !serviceImpl.saveBatch(list)) throw new APIException("easyExcel执行批量导入出错!");
                if(!serviceImpl.save(entry)) throw new APIException("easyExcel执行单行导入出错!");
            }
        }catch (Exception e){
            log.error("后置事件执行错误!", e);
            throw new APIException("后置事件执行错误!");
        }
    }

    /**
     * 从excel文件里面查出来信息，然后返回到集合中
     * @param file    文件数据源
     * @param clazz   dto类
     * @param <T>     dto泛型
     * @return        dto集合
     */
    public static <T> List<T> doRead(MultipartFile file, Class<T> clazz){
        AtomicBoolean flag = new AtomicBoolean(true);
        List<T> list = new ArrayList<>();
        try {
            EasyExcel.read(file.getInputStream(), clazz, new PageReadListener<T>(dataList -> {
                list.addAll(dataList);
            })).sheet().headRowNumber(3).doRead();;
        }catch (IOException e){
            log.error("easyExcel读取excel文件失败!", e);
            flag.set(false);
        }
        return flag.get() ? list : new ArrayList<>();
    }

}
